/* eslint-disable @typescript-eslint/no-explicit-any */
import { cls } from '@knno/jsx';
import type { SelectType } from '../../list/list';
import { PopList, type PopListSelectHandler } from '../../popList/popList';
import { DATA } from '../../symbol';
import { cellStyle } from '../cell.css';
import { inlineStyle } from '../inline.css';
import { InputBase, type InputBaseProps } from '../inputBase';
import { selectInputStyle } from './select.css';

export type SelectOptionInfo = {
	value?: any;
	text: string;
	icon?: string;
	children?: SelectOptionInfo[];
};

export type SelectValue = {
	value: any;
	text: string;
};

export type SelectOptions =
	| SelectOptionInfo[]
	| (() => Promise<SelectOptionInfo[]> | SelectOptionInfo[]);

export type SelectInputProps<T extends SelectInputBase<any, any, any>> = InputBaseProps<
	T,
	SelectValue[]
> & {
	multiple?: boolean;
	multiSelectType?: SelectType;
	searchBox?: boolean;
	popupWidth?: number | string;
	/**
	 * Whether to return the full path of the selected item.
	 * If set to true, the result will be a full path joined by separator ' / '.
	 * If set to a string, the result will be a full path joined by the separator.
	 */
	returnFullPath?: boolean | string;
	options?: SelectOptions;
	minCount?: number;
	maxCount?: number;
};

abstract class SelectInputBase<
	P extends SelectInputProps<C>,
	C extends SelectInputBase<P, C, E>,
	E extends HTMLElement,
> extends InputBase<P, C, E, SelectValue[]> {
	[DATA]?: PopListSelectHandler;
	constructor(props: P) {
		super(props);
		this.setOptions(this.options);
		this.setMinCount(this.minCount);
		this.setMaxCount(this.maxCount);
		this.setMultiple(this.multiple);
		this.el.tabIndex = 0;
		this.el.classList.add(selectInputStyle);
		this.el.addEventListener('click', () => {
			if (this.readOnly) return;
			this.openSelect();
		});
		this.el.addEventListener('keypress', (evt) => {
			if (this.readOnly) return;
			if (evt.key === ' ' || evt.key === 'Space' || evt.key === 'Enter') {
				evt.preventDefault();
				evt.stopPropagation();
				if (!this.readOnly) {
					this.openSelect();
				}
			}
		});
	}
	protected override renderContent(): void {
		this.el.textContent = this.value?.map((item) => item.text).join('; ') ?? '';
	}
	protected override changeReadOnly(): void {}

	private async openSelect() {
		if (this[DATA]) return;
		let options: SelectOptionInfo[] = [];
		if (typeof this.options === 'function') {
			options = await this.options();
		} else {
			options = this.options;
		}
		this[DATA] = PopList.show(options, {
			refer: this.el,
			columnOption: {
				text: 'text',
				value: 'value',
				children: 'children',
				expand: 'expand',
			},
			activeValue: this.multiple ? undefined : this.value?.[0],
			selected: this.multiple ? this.value : undefined,
			useSearch: this.searchBox ?? false,
			width: this.popupWidth,
			multiSelect: this.multiple,
			multiSelectType: this.multiSelectType,
			returnFullPath: this.returnFullPath,
			focusOnClose: this.el,
			focus: true,
		})
			.onSelect((value, text) => {
				if (value === null || value === undefined) {
					this.el.textContent = '';
					this.updateValue(undefined);
				} else {
					this.el.textContent = text + '';
					this.updateValue([{ value, text }]);
				}
			})
			.onSelectMultiple((selected) => {
				this.el.textContent = selected.map((item) => item.text).join('; ');
				this.updateValue(selected.map((item) => ({ value: item.value, text: item.text })));
			})
			.onClose(() => {
				delete this[DATA];
			});
	}
	get searchBox(): boolean {
		return this.props.searchBox ?? false;
	}
	setSearchBox(searchBox: boolean): this {
		this.props.searchBox = searchBox;
		return this;
	}
	get returnFullPath(): boolean | string {
		return this.props.returnFullPath ?? false;
	}
	setReturnFullPath(returnFullPath: boolean | string): this {
		this.props.returnFullPath = returnFullPath;
		return this;
	}
	get popupWidth(): number | string | undefined {
		return this.props.popupWidth;
	}
	setPopupWidth(popupWidth: number | string | undefined): this {
		this.props.popupWidth = popupWidth;
		return this;
	}
	get options(): SelectOptions {
		return this.props.options ?? [];
	}
	setOptions(options: SelectOptions): this {
		this.props.options = options;
		this.renderContent();
		return this;
	}
	get minCount(): number | undefined {
		return this.props.minCount;
	}
	setMinCount(value: number | undefined): this {
		this.props.minCount = value;
		return this;
	}
	get maxCount(): number | undefined {
		return this.props.maxCount;
	}
	setMaxCount(value: number | undefined): this {
		this.props.maxCount = value;
		this.renderContent();
		return this;
	}
	get multiple(): boolean {
		return this.props.multiple ?? false;
	}
	setMultiple(multiple: boolean): this {
		this.props.multiple = multiple;
		this.renderContent();
		return this;
	}
	get multiSelectType(): SelectType {
		return this.props.multiSelectType ?? 'any';
	}
	setMultiSelectType(multiSelectType: SelectType): this {
		this.props.multiSelectType = multiSelectType;
		return this;
	}
}

export type SelectCellProps = SelectInputProps<SelectCell> & {
	colSpan?: number;
	rowSpan?: number;
};

export class SelectCell extends SelectInputBase<SelectCellProps, SelectCell, HTMLTableCellElement> {
	protected render(): Node {
		return (
			<td
				class={cls(this.props.class, cellStyle)}
				style={this.props.style}
				colSpan={this.colSpan}
				rowSpan={this.rowSpan}
			/>
		);
	}
	get colSpan(): number {
		return this.props.colSpan ?? 1;
	}

	setColSpan(value: number): this {
		this.props.colSpan = value;
		this.el.colSpan = value;
		return this;
	}

	get rowSpan(): number {
		return this.props.rowSpan ?? 1;
	}

	setRowSpan(value: number): this {
		this.props.rowSpan = value;
		this.el.rowSpan = value;
		return this;
	}
}

export class SelectInline extends SelectInputBase<
	SelectInputProps<SelectInline>,
	SelectInline,
	HTMLSpanElement
> {
	protected render(): Node {
		return <span class={cls(this.props.class, inlineStyle)} style={this.props.style} />;
	}
}
